package de.uni_saarland.coli.doclists.data.btree.nodes;

import java.io.Serializable;

import de.uni_saarland.coli.doclists.data.btree.BTree;
import de.uni_saarland.coli.doclists.data.btree.INode;
import de.uni_saarland.coli.doclists.data.btree.SplitData;
import de.uni_saarland.coli.doclists.index.filesystem.FilesystemHelper;

/**
 * 
 * @author stefan
 * 
 *         Bucket nodes contain the linked nodes and the corresponding keys.
 * 
 * @param <T>
 *            The type of data.
 */
public class NonBucketNode<T> extends BaseNode<T> implements Serializable{
	/**
	 * 
	 */
	private static final long serialVersionUID = -5887887131737104338L;

	/**
	 * the nodes
	 */
	private int[] nodes;

	/**
	 * number of nodes
	 */
	private int nodeCount;
	
	public void updateNodeCount(){
		this.nodeCount = this.nodes.length;
	}

	// private ArrayList<INode<T>> nodes;

	/**
	 * Constructor.
	 */
	public NonBucketNode() {
		this.initialize();
		this.nodes = new int[4];
		this.nodeCount = 0;
		// this.nodes = new ArrayList<INode<T>>();
	}

	/**
	 * Constructor
	 * 
	 * @param bytes
	 *            serialized data
	 */
	public NonBucketNode(byte[] bytes) {
		this.initialize();
		this.nodes = new int[4];
		this.nodeCount = bytes[1];
		
		for (int i = 0; i < 3; i++) {
			this.childIds[i] = FilesystemHelper.getIntFromByteArray(bytes,
					2 + i * 4);
		}

		for (int i = 0; i < 4; i++) {
			this.nodes[i] = FilesystemHelper.getIntFromByteArray(bytes,
					14 + i * 4);
		}
	}

	/**
	 * Creates a new node using the split data.
	 * 
	 * @param split
	 *            the split data
	 */
	public NonBucketNode(SplitData<T> split) {
		this.initialize();
		this.nodes = new int[4];
		this.nodeCount = 2;
		this.childIds[0] = split.getKey();
		this.nodes[0] = split.getLeft().getNodeID();
		this.nodes[1] = split.getRight().getNodeID();
		
	}

	@Override
	public boolean isBucket() {
		return false;
	}

	@Override
	public boolean isNotBucket() {
		return true;
	}

	@SuppressWarnings("unchecked")
	@Override
	public SplitData<T> split() {
		System.out.println("split non bucket");
		System.out.println("This Node before splitting:\n" + this);
		// create new left node
		/*NonBucketNode<T> left = (NonBucketNode<T>) BTree.getNodeFactory()
				.createNonBucketNode();
		left.childIds[0] = this.childIds[0];
		left.childIds[1] = this.childIds[1];
		left.nodes[0] = this.nodes[0];
		left.nodes[1] = this.nodes[1];
		left.nodeCount = 2;*/
		// create new right node
		NonBucketNode<T> right = (NonBucketNode<T>) BTree.getNodeFactory()
				.createNonBucketNode();
		System.err.println("Right before adding: " + right);
		right.childIds[0] = this.childIds[2];
		for (int i = 2; i < 4; i++) {
			System.out.println("index:" + i);
			System.out.println("adding " + this.nodes[i]);
			right.nodes[i - 2] = this.nodes[i];
		}

		System.err.println("Right after adding: " + right);
		
		System.err.println("My nodecount: " + this.nodeCount);
		
		right.nodeCount = this.nodeCount - 2;
		//right.updateNodeCount();
		
		System.err.println("My nodecount after sub: " + this.nodeCount);
		System.err.println(right.getClass());
		System.err.println("Right after substracting: " + right);
		
		this.childIds[2] = 0;
		this.nodes[2] = Integer.MAX_VALUE;
		this.nodes[3] = Integer.MAX_VALUE;
		this.nodeCount = 2;
				
		SplitData<T> splitResult = new SplitData<T>();
		splitResult.setKey(this.childIds[1]);
		splitResult.setLeft(this);
		splitResult.setRight(right);
		
		System.err.println("Splittet non-bucket key: " + splitResult.getKey());
		System.err.println("Splittet non-bucket left: " + splitResult.getLeft());
		System.err.println("Splittet non-bucket right: " + splitResult.getRight());

		System.out.println("done");
		this.updateDistListEntry();
		return splitResult;
	}

	/**
	 * Shifts the nodes one to the left starting at the index.
	 * 
	 * @param idx
	 *            the index
	 */
	public void shiftNodesLeft(int idx) {
		for (int i = 3; i > idx; i--) {
			this.nodes[i] = this.nodes[i - 1];
		}
	}

	/**
	 * inserts data at an child index.
	 * 
	 * @param idx
	 *            the index
	 * @param data
	 *            the data
	 */
	private void insertSplitDate(int idx, SplitData<T> data) {
		this.shiftKeysLeft(idx);
		// if (idx < this.nodeCount)
		// this.nodes.remove(idx);

		this.childIds[idx] = data.getKey();
		this.nodes[idx] = data.getRight().getNodeID();
		this.shiftNodesLeft(idx);
		this.nodes[idx] = data.getLeft().getNodeID();

		this.nodeCount++;

		this.updateDistListEntry();
	}

	/**
	 * Merges new data at the index.
	 * 
	 * @param idx
	 *            the index.
	 * @param data
	 *            the data
	 * @return Split infos.
	 */
	private SplitData<T> merge(int idx, SplitData<T> data) {
		if (!this.isFull()) {
			this.insertSplitDate(idx, data);
			return null;
		} else {
			SplitData<T> splitResult = this.split();

			if (data.getKey() > splitResult.getKey()) {
				int tIDX = splitResult.getRight().findLink(data.getKey());
				((NonBucketNode<T>) splitResult.getRight()).insertSplitDate(
						tIDX, data);
				((NonBucketNode<T>) splitResult.getRight()).updateDistListEntry();
			} else {
				int tIDX = splitResult.getLeft().findLink(data.getKey());
				((NonBucketNode<T>) splitResult.getLeft()).insertSplitDate(
						tIDX, data);
				((NonBucketNode<T>) splitResult.getLeft()).updateDistListEntry();
			}

			return splitResult;
		}
	}

	@Override
	public SplitData<T> addData(int key, T value) {
		System.err.println("Adding " + key + " : " + value + " to a " + this.getClass());
		int idx = this.findLink(key);

		if (this.nodeCount <= idx) {
			this.shiftNodesLeft(idx);
			this.nodes[idx] = BTree.getNodeFactory().createBucketNode()
					.getNodeID();
			// this.nodes.add(idx, (BucketNode<T>) BTree.getNodeFactory()
			// .createBucketNode());
			this.shiftKeysLeft(idx);
			if (idx < 3)
				this.childIds[idx] = key;
			this.nodeCount++;
			
			this.updateDistListEntry();
		}
		SplitData<T> splitData = this.getNodeAt(idx).addData(key, value);

		if (splitData != null) {
			return this.merge(idx, splitData);
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	private INode<T> getNodeAt(int idx) {
		System.out.println("N:" + this.nodes[idx]);
		return BTree.getNodeFactory().getNode(this.nodes[idx]);
	}

	@Override
	public T get(int key) {
		int idx = this.findLink(key);

		if (idx < this.nodeCount)
			return (T) this.getNodeAt(idx).get(key);

		return null;
	}

	@Override
	public void toStingHelper(String delim, StringBuilder sb) {
		int len = this.nodeCount;
		if (len > 0)
			this.getNodeAt(0).toStingHelper(delim + " ", sb);
		for (int i = 0; i < 3 && this.childIds[i] != Integer.MAX_VALUE; i++) {
			sb.append(delim).append(this.childIds[i]).append("\n");
			if (len > (i + 1))
				this.getNodeAt(i + 1).toStingHelper(delim + " ", sb);
			else
				sb.append("\n");
		}
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		this.toStingHelper("", sb);
		return sb.toString();
	}

	@Override
	public byte[] toBytes() {
		byte[] bytes = new byte[30];

		bytes[0] = 0;
		bytes[1] = (byte) this.nodeCount;

		for (int i = 0; i < 3; i++) {
			FilesystemHelper.addIntToByteArray(bytes, this.childIds[i],
					2 + i * 4);
		}

		for (int i = 0; i < 4; i++) {
			FilesystemHelper
					.addIntToByteArray(bytes, this.nodes[i], 14 + i * 4);
		}

		return bytes;
	}
}
